package com.clei.utils;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 任务执行重试 util
 *
 * @author Y
 * @date 2022-08-30
 */
public class RetryUtil {
    /**
     * 等待时长
     * 第一次3 u
     * 第二次5 u
     * 第三次10 u
     * 0 大于等于第四次10 u
     */
    private static final int[] WAIT_ARR = {10, 3, 5, 10};

    private RetryUtil() {
    }

    /**
     * 执行任务
     *
     * @param retryTimes 重试充数 -1无限
     * @param task       task
     */
    public static void execTask(Supplier<Boolean> task, int retryTimes) {
        execTask(task, retryTimes, null, null);
    }

    /**
     * 执行任务
     *
     * @param task       task
     * @param retryTimes 重试充数 -1无限
     * @param waitArr    重试等待时间arr
     * @param timeUnit   等待时间时间单位
     */
    public static void execTask(Supplier<Boolean> task, int retryTimes, int[] waitArr, TimeUnit timeUnit) {
        // 首次执行
        Boolean success = task.get();
        // 首次执行失败才重试
        if (!success) {
            waitArr = null == waitArr ? WAIT_ARR : waitArr;
            timeUnit = null == timeUnit ? TimeUnit.MINUTES : timeUnit;
            new RetryExecutor().execute(task, retryTimes, waitArr, timeUnit);
        }
    }

    /**
     * 重试执行器
     */
    private static class RetryExecutor {
        /**
         * 定时执行器
         */
        private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();

        /**
         * 等待时长 arr
         */
        private int[] waitArr;

        /**
         * 等待时长单位
         */
        private TimeUnit timeUnit;

        /**
         * 重试次数限制
         */
        private int retryTimes;

        /**
         * 第n次重试
         */
        private int times;

        /**
         * 执行任务
         *
         * @param task       task
         * @param retryTimes 重试充数 -1无限
         * @param waitArr    重试等待时间arr
         * @param timeUnit   等待时间时间单位
         */
        public void execute(Supplier<Boolean> task, int retryTimes, int[] waitArr, TimeUnit timeUnit) {
            this.waitArr = waitArr;
            this.timeUnit = timeUnit;
            this.retryTimes = retryTimes;
            this.times = 1;
            this.scheduledExecutor.schedule(() -> executeTask(task), this.waitArr[times], this.timeUnit);
        }

        /**
         * 执行任务
         *
         * @param task task
         */
        private void executeTask(Supplier<Boolean> task) {
            boolean success = task.get();
            if (success) {
                // 成功后关闭线程池
                this.scheduledExecutor.shutdown();
            } else {
                this.times++;
                if (-1 == this.retryTimes || this.retryTimes >= this.times) {
                    int wait = this.times > waitArr.length - 1 ? waitArr[0] : waitArr[this.times];
                    this.scheduledExecutor.schedule(() -> executeTask(task), wait, this.timeUnit);
                } else {
                    this.scheduledExecutor.shutdown();
                }
            }
        }
    }
}
